package com.choosefine.paycenter.common.aop;

import com.choosefine.paycenter.common.dto.FieldValidError;
import com.choosefine.paycenter.common.dto.JSONResult;
import com.choosefine.paycenter.common.exception.BusinessException;
import com.choosefine.paycenter.common.exception.ParamValidException;
import com.choosefine.paycenter.common.utils.JSONResultGenerator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by Jay Chang on 2017/3/4.
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler  {

    @ExceptionHandler(HttpMessageNotReadableException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public JSONResult httpMessageNotReadableException(HttpServletRequest req,HttpMessageNotReadableException e){
        log.error(e.getMessage());
        return JSONResultGenerator.buildHttpMessageNotReadableError();
    }

    /**
     * 请求方法不支持
     * @param req
     * @param e
     * @return
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    public JSONResult methodNotSupportedException(HttpServletRequest req,HttpRequestMethodNotSupportedException e){
        return JSONResultGenerator.buildMethodNotSupportError(req);
    }

    /**
     * 当Controller的方法里同时使用@Valid|@Validated与@RequestBody时，校验失败会抛MethodArgumentNotValidException
     * @param req
     * @param e
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.OK)
    public JSONResult methodArgumentNotValidException(HttpServletRequest req,MethodArgumentNotValidException e){
        BindingResult bindingResult = e.getBindingResult();
        List<FieldValidError> fieldValidErrors = doGetFieldValidErrors(bindingResult.getFieldErrors());
        return JSONResultGenerator.buildValidError(fieldValidErrors);
    }

    /**
     * Controller方法实体类中字段的校验
     * @param req
     * @param e
     * @return
     */
    @ExceptionHandler({BindException.class})
    @ResponseStatus(HttpStatus.OK)
    public JSONResult<List<FieldValidError>> bindException(HttpServletRequest req,BindException e){
        log.debug("GlobalExceptionHandler.bindExceptionHaneler,req url[{}]",req.getRequestURL());
        List<FieldError> fieldErrors = e.getFieldErrors();
        //将FieldError类型转换为FieldValidError
        List<FieldValidError> fieldValidErrors = doGetFieldValidErrors(fieldErrors);
        return JSONResultGenerator.buildValidError(fieldValidErrors);
    }

    private List<FieldValidError> doGetFieldValidErrors(List<FieldError> fieldErrors) {
        List<FieldValidError> fieldValidErrors = new ArrayList<>();
        for(FieldError fieldError :fieldErrors){
            FieldValidError fieldValidError = new FieldValidError(fieldError.getField(),fieldError.getDefaultMessage());
            fieldValidErrors.add(fieldValidError);
        }
        return fieldValidErrors;
    }

    /**
     * 处理未定义的异常(这里指Spring不认识的异常，比如我们自定义的异常)
     * @param req
     * @param undeclaredThrowableException 切面内抛出的异常都会被 UndeclaredThrowableException包装
     * @return
     */
    @ExceptionHandler(UndeclaredThrowableException.class)
    @ResponseStatus(HttpStatus.OK)
    public JSONResult undeclaredThrowableException(HttpServletRequest req, UndeclaredThrowableException undeclaredThrowableException){
        Throwable throwable = undeclaredThrowableException.getUndeclaredThrowable(); // 获得实际异常
        if(throwable instanceof ParamValidException){//若是我们自定义的参数校验不通过的异常
            return JSONResultGenerator.buildValidError(((ParamValidException) throwable).getFieldValidErrors());
        }
        return exception(throwable);
    }

    /**
     * 业务异常统一处理
     * @param e
     * @return
     */
    @ExceptionHandler(BusinessException.class)
    @ResponseStatus(HttpStatus.OK)
    public JSONResult businessException(HttpServletRequest req, BusinessException e){
        return JSONResultGenerator.buildBusinessError(e);
    }

    /**
     * 无法处理的异常统一由这个方法来处理
     *
     * @param e
     * @return
     */
    @ExceptionHandler(Throwable.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public JSONResult exception(Throwable e) {
        log.error(e.getMessage(),e);
        return JSONResultGenerator.buildError(e);
    }
}
